package git.soulbgm.job;

import java.util.*;

import git.soulbgm.common.enums.Status;
import git.soulbgm.common.okhttp.OkHttpUtil;
import git.soulbgm.config.SysConfig;
import git.soulbgm.pojo.*;
import git.soulbgm.pojo.dto.NtpdateInfo;
import git.soulbgm.service.AlarmInfoReport;
import git.soulbgm.service.AlarmRuleInfoService;
import git.soulbgm.service.NodeInfoService;
import git.soulbgm.service.NodeNtpdateStatusService;
import git.soulbgm.utils.JsonTool;
import git.soulbgm.utils.LogHelper;
import git.soulbgm.utils.StringUtil;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static git.soulbgm.common.MonitorConstants.*;

/**
 * 设备节点时间同步监测任务
 *
 * @author SoulBGM
 * @date 2021/01/29
 */
@Service
public class NodeNtpdateMonitorTask {

    private static final LogHelper LOG = new LogHelper(NodeHealthMonitorTask.class);
    private static final String RULE_TYPE = RULE_TYPE_NODE;

    private final NodeInfoService nodeInfoService;
    private final NodeNtpdateStatusService nodeNtpdateStatusService;
    private final AlarmRuleInfoService alarmRuleInfoService;
    private final AlarmInfoReport alarmInfoReport;
    private final Integer currentRegionCode;

    public NodeNtpdateMonitorTask(NodeInfoService nodeInfoService, NodeNtpdateStatusService nodeNtpdateStatusService, AlarmRuleInfoService alarmRuleInfoService, AlarmInfoReport alarmInfoReport, SysConfig config) {
        this.nodeInfoService = nodeInfoService;
        this.nodeNtpdateStatusService = nodeNtpdateStatusService;
        this.alarmRuleInfoService = alarmRuleInfoService;
        this.alarmInfoReport = alarmInfoReport;
        this.currentRegionCode = config.getRegionCode();
    }

    @Scheduled(cron = "${jobs.map.nodeNtpdateMonitoring.cron}")
    @SchedulerLock(name = "${jobs.map.nodeNtpdateMonitoring.name}", lockAtMostFor = "${jobs.map.nodeNtpdateMonitoring.lockAtMostFor}", lockAtLeastFor = "${jobs.map.nodeNtpdateMonitoring.lockAtLeastFor}")
    public void execute() throws Exception {
        Date collectTime = new Date();
        Map<Long, NodeInfo> map = nodeInfoService.getCacheNodeInfoAll().stream()
                .filter(o -> Status.NORMAL.code.equals(o.getStatus()) && MONITOR_MODE_PASSIVITY == o.getMonitorMode()).collect(Collectors.toMap(NodeInfo::getId, Function.identity()));
        if (map.isEmpty()) {
            return;
        }
        List<NodeNtpdateStatus> statusList = map.values().stream()
                .map(o -> getNodeNtpdateStatus(o, collectTime)).collect(Collectors.toList());
        int size = map.size();
        Map<String, List<Double>> alarmRuleMap = alarmRuleInfoService.getAlarmRuleMap(RULE_TYPE);
        CountDownLatch latch = new CountDownLatch(size);
        for (NodeNtpdateStatus ntpdateStatus : statusList) {
            NodeInfo info = map.get(ntpdateStatus.getId());
            String url = StringUtil.format("http://{}:{}{}", info.getIp(), info.getPort(), TIME_SYNC_URL);
            OkHttpUtil.get(url, new NodeNtpdateMonitorCallback(ntpdateStatus, latch));
        }

        boolean isExecuteComplete = latch.await(10L * size, TimeUnit.SECONDS);
        if (!isExecuteComplete) {
            LOG.error("执行节点时间同步监测时间超时");
        }

        for (NodeNtpdateStatus ntpdateStatus : statusList) {
            recordAlarm(ntpdateStatus, alarmRuleMap);
        }

        nodeNtpdateStatusService.reset(statusList);
    }

    /**
     * 获得节点时间同步状态
     *
     * @param info        信息
     * @param collectTime 收集时间
     * @return {@link NodeNtpdateStatus}
     */
    private NodeNtpdateStatus getNodeNtpdateStatus(NodeInfo info, Date collectTime) {
        NodeNtpdateStatus ntpdateStatus = new NodeNtpdateStatus();
        ntpdateStatus.setId(info.getId());
        ntpdateStatus.setNodeCode(info.getNodeCode());
        ntpdateStatus.setDeployIp(info.getIp());
        ntpdateStatus.setDetectionTime(collectTime);
        ntpdateStatus.setNtpServerIp("");
        ntpdateStatus.setOffsetSeconds(0.0D);
        ntpdateStatus.setRegionCode(currentRegionCode);
        ntpdateStatus.setStatus(Status.NTP_FAIL.code);
        return ntpdateStatus;
    }

    /**
     * 记录报警
     *
     * @param nodeNtpdateStatus 节点状态
     */
    public void recordAlarm(NodeNtpdateStatus nodeNtpdateStatus, Map<String, List<Double>> map) {
        recordAlarm(nodeNtpdateStatus, RULE_TYPE,
                EVENT_CODE_TIME_SYNC_SERVER_NOT_ACCESS, map.get(EVENT_CODE_TIME_SYNC_SERVER_NOT_ACCESS), Status.NTP_UNAVAILABLE.code.equals(nodeNtpdateStatus.getStatus()) ? 0D : -1D);
        recordAlarm(nodeNtpdateStatus, RULE_TYPE,
                EVENT_CODE_TIME_SYNC_FAIL, map.get(EVENT_CODE_TIME_SYNC_FAIL), Status.NTP_FAIL.code.equals(nodeNtpdateStatus.getStatus()) ? 0D : -1D);
        if (nodeNtpdateStatus.getOffsetSeconds() != null) {
            recordAlarm(nodeNtpdateStatus, RULE_TYPE,
                    EVENT_CODE_TIME_SYNC_EXCEED_LIMIT, map.get(EVENT_CODE_TIME_SYNC_EXCEED_LIMIT), Math.abs(nodeNtpdateStatus.getOffsetSeconds()) * 1000);
        }
    }

    /**
     * 记录告警
     *
     * @param nodeNtpdateStatus 节点时间同步状态信息
     * @param ruleType          规则类型
     * @param eventCode         事件编号
     * @param thresholdList     阈值列表
     * @param value             值
     */
    private void recordAlarm(NodeNtpdateStatus nodeNtpdateStatus, String ruleType, String eventCode, List<Double> thresholdList, Double value) {
        boolean flag = false;
        for (Double threshold : thresholdList) {
            if (threshold <= value) {
                flag = true;
                break;
            }
        }
        if (flag) {
            String alarmContent = StringUtil.format("获取来自IP为 {} 的时间同步情况，触发了 {} 的告警，NTP服务器为{}{}", nodeNtpdateStatus.getDeployIp(), getEventContent(eventCode), nodeNtpdateStatus.getNtpServerIp(), eventCode == EVENT_CODE_TIME_SYNC_EXCEED_LIMIT ? ", 与参考时间源相差" + value + "毫秒" : "");
            LOG.warn(alarmContent);
            alarmInfoReport.nodeTimeSyncExceptionAlarm(nodeNtpdateStatus.getNodeCode(), ruleType, eventCode, alarmContent, value);
        } else {
            alarmInfoReport.nodeTimeSyncExceptionClearAlarm(nodeNtpdateStatus.getNodeCode(), ruleType, eventCode, value);
        }
    }

}

/**
 * 设备节点时间同步监测回调
 *
 * @author SoulBGM
 * @date 2023-01-06
 */
class NodeNtpdateMonitorCallback implements Callback {

    private final NodeNtpdateStatus ntpdateStatus;
    private final CountDownLatch latch;

    public NodeNtpdateMonitorCallback(NodeNtpdateStatus ntpdateStatus, CountDownLatch latch) {
        this.ntpdateStatus = ntpdateStatus;
        this.latch = latch;
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ResponseBody body = response.body();
        if (body != null) {
            NtpdateInfo ntpdateInfo = JsonTool.parse(body.string(), NtpdateInfo.class);
            if (ntpdateInfo == null) {
                return;
            }
            ntpdateStatus.setNtpServerIp(ntpdateInfo.getNtpIp());
            ntpdateStatus.setOffsetSeconds(ntpdateInfo.getOffsetSeconds());
            ntpdateStatus.setStatus(ntpdateInfo.getStatus());
        }
        latch.countDown();
    }

    @Override
    public void onFailure(Call call, IOException e) {
        latch.countDown();
    }

}
